package evl

import (
	"runtime"
	"testing"

	"gitcode.com/deyiyangyang/bampoo/ast"
	"gitcode.com/deyiyangyang/bampoo/obj"
)

// TestMemoryLeakInBegin tests for memory leaks in begin expressions
func TestMemoryLeakInBegin(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Get initial memory stats
	var m1, m2 runtime.MemStats
	runtime.GC()
	runtime.ReadMemStats(&m1)

	// Create a begin expression that creates temporary objects
	begin := &ast.BeginExpr{
		Body: []ast.Expression{
			&ast.DefineExpr{
				Name:  ast.Symbol{Value: "temp1"},
				Value: &ast.IntegerLiteral{Value: 100},
			},
			&ast.DefineExpr{
				Name:  ast.Symbol{Value: "temp2"},
				Value: &ast.StringLiteral{Value: "temporary"},
			},
			&ast.ArithmeticExpr{
				Operator: "+",
				Operands: []ast.Expression{
					&ast.Symbol{Value: "temp1"},
					&ast.IntegerLiteral{Value: 50},
				},
			},
		},
	}

	// Run the begin expression multiple times
	for i := 0; i < 1000; i++ {
		_, err := evaluator.Eval(begin)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
	}

	// Force garbage collection and check memory
	runtime.GC()
	runtime.ReadMemStats(&m2)

	// Calculate absolute difference to handle possible memory decrease
	var memIncrease uint64
	if m2.Alloc > m1.Alloc {
		memIncrease = m2.Alloc - m1.Alloc
	} else {
		memIncrease = m1.Alloc - m2.Alloc
	}
	
	// The memory difference should be reasonable (not indicating a massive leak)
	if memIncrease > 1024*1024 { // More than 1MB difference is suspicious
		t.Logf("Warning: Memory difference of %d bytes detected", memIncrease)
		t.Logf("Initial alloc: %d, Final alloc: %d", m1.Alloc, m2.Alloc)
		// Don't fail the test but log the warning
	}
}

// TestFunctionCallMemoryManagement tests memory management in function calls
func TestFunctionCallMemoryManagement(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Define a simple function
	define := &ast.DefineExpr{
		Name: ast.Symbol{Value: "test-func"},
		Value: &ast.LambdaExpr{
			Parameters: []ast.Symbol{{Value: "x"}},
			Body: []ast.Expression{
				&ast.ArithmeticExpr{
					Operator: "+",
					Operands: []ast.Expression{
						&ast.Symbol{Value: "x"},
						&ast.IntegerLiteral{Value: 1},
					},
				},
			},
		},
	}

	_, err := evaluator.Eval(define)
	if err != nil {
		t.Fatalf("unexpected error defining function: %v", err)
	}

	// Get memory stats before function calls
	var m1, m2 runtime.MemStats
	runtime.GC()
	runtime.ReadMemStats(&m1)

	// Call the function many times
	for i := 0; i < 1000; i++ {
		call := &ast.List{
			Elements: []ast.Expression{
				&ast.Symbol{Value: "test-func"},
				&ast.IntegerLiteral{Value: int64(i)},
			},
		}

		_, err := evaluator.Eval(call)
		if err != nil {
			t.Fatalf("unexpected error calling function: %v", err)
		}
	}

	// Check memory after function calls
	runtime.GC()
	runtime.ReadMemStats(&m2)

	// Calculate absolute difference to handle possible memory decrease
	var memIncrease uint64
	if m2.Alloc > m1.Alloc {
		memIncrease = m2.Alloc - m1.Alloc
	} else {
		memIncrease = m1.Alloc - m2.Alloc
	}
	
	if memIncrease > 512*1024 { // More than 512KB increase is suspicious
		t.Logf("Warning: Memory difference of %d bytes detected", memIncrease)
		t.Logf("Initial alloc: %d, Final alloc: %d", m1.Alloc, m2.Alloc)
	}
}

// TestCircularReferenceAvoidance tests that we don't create circular references
func TestCircularReferenceAvoidance(t *testing.T) {
	env := obj.NewEnvironment()
	evaluator := New(env)

	// Create a function that references itself (potential circular reference)
	define := &ast.DefineExpr{
		Name: ast.Symbol{Value: "recursive-func"},
		Value: &ast.LambdaExpr{
			Parameters: []ast.Symbol{{Value: "n"}},
			Body: []ast.Expression{
				&ast.BeginExpr{
					Body: []ast.Expression{
						&ast.DisplayExpr{
							Value: &ast.StringLiteral{Value: "recursion test"},
						},
						&ast.Symbol{Value: "n"},
					},
				},
			},
		},
	}

	result, err := evaluator.Eval(define)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Verify the function was created
	if _, ok := result.(*obj.NilValue); !ok {
		t.Fatalf("expected NilValue from define, got %T", result)
	}

	// Verify the function is stored correctly
	funcVal, ok := env.Get("recursive-func")
	if !ok {
		t.Fatal("expected to find recursive-func in environment")
	}

	fn, ok := funcVal.(*obj.FunctionValue)
	if !ok {
		t.Fatalf("expected FunctionValue, got %T", funcVal)
	}

	// Check that the InEnvironment flag is set
	if !fn.InEnvironment {
		t.Error("expected InEnvironment flag to be true for function stored in environment")
	}
}